/**
 * @brief 基础工具函数的封装
 * @brief
 * 1.字符串分割函数
 * 2.读写数据函数(写入到buffer中)
 * 3.url编码与解码函数
 * 4.响应状态码的描述信息获取
 * 5.根据文件后缀名获取文件mime
 * 6.判断一个文件是否是一个目录
 * 7.判断一个文件是否是一个普通文件
 * 8.http请求的资源路径有效性判断
 * 9.SIGPIP信号的屏蔽
 */
#pragma once

#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <signal.h>

#include "logger.hpp"

namespace hdp
{
    std::unordered_map<int, std::string> _statu_msg = {
        {100, "Continue"},
        {101, "Switching Protocol"},
        {102, "Processing"},
        {103, "Early Hints"},
        {200, "OK"},
        {201, "Created"},
        {202, "Accepted"},
        {203, "Non-Authoritative Information"},
        {204, "No Content"},
        {205, "Reset Content"},
        {206, "Partial Content"},
        {207, "Multi-Status"},
        {208, "Already Reported"},
        {226, "IM Used"},
        {300, "Multiple Choice"},
        {301, "Moved Permanently"},
        {302, "Found"},
        {303, "See Other"},
        {304, "Not Modified"},
        {305, "Use Proxy"},
        {306, "unused"},
        {307, "Temporary Redirect"},
        {308, "Permanent Redirect"},
        {400, "Bad Request"},
        {401, "Unauthorized"},
        {402, "Payment Required"},
        {403, "Forbidden"},
        {404, "Not Found"},
        {405, "Method Not Allowed"},
        {406, "Not Acceptable"},
        {407, "Proxy Authentication Required"},
        {408, "Request Timeout"},
        {409, "Conflict"},
        {410, "Gone"},
        {411, "Length Required"},
        {412, "Precondition Failed"},
        {413, "Payload Too Large"},
        {414, "URI Too Long"},
        {415, "Unsupported Media Type"},
        {416, "Range Not Satisfiable"},
        {417, "Expectation Failed"},
        {418, "I'm a teapot"},
        {421, "Misdirected Request"},
        {422, "Unprocessable Entity"},
        {423, "Locked"},
        {424, "Failed Dependency"},
        {425, "Too Early"},
        {426, "Upgrade Required"},
        {428, "Precondition Required"},
        {429, "Too Many Requests"},
        {431, "Request Header Fields Too Large"},
        {451, "Unavailable For Legal Reasons"},
        {501, "Not Implemented"},
        {502, "Bad Gateway"},
        {503, "Service Unavailable"},
        {504, "Gateway Timeout"},
        {505, "HTTP Version Not Supported"},
        {506, "Variant Also Negotiates"},
        {507, "Insufficient Storage"},
        {508, "Loop Detected"},
        {510, "Not Extended"},
        {511, "Network Authentication Required"}};

    std::unordered_map<std::string, std::string> _mime_msg = {
        {".aac", "audio/aac"},
        {".abw", "application/x-abiword"},
        {".arc", "application/x-freearc"},
        {".avi", "video/x-msvideo"},
        {".azw", "application/vnd.amazon.ebook"},
        {".bin", "application/octet-stream"},
        {".bmp", "image/bmp"},
        {".bz", "application/x-bzip"},
        {".bz2", "application/x-bzip2"},
        {".csh", "application/x-csh"},
        {".css", "text/css"},
        {".csv", "text/csv"},
        {".doc", "application/msword"},
        {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
        {".eot", "application/vnd.ms-fontobject"},
        {".epub", "application/epub+zip"},
        {".gif", "image/gif"},
        {".htm", "text/html"},
        {".html", "text/html"},
        {".ico", "image/vnd.microsoft.icon"},
        {".ics", "text/calendar"},
        {".jar", "application/java-archive"},
        {".jpeg", "image/jpeg"},
        {".jpg", "image/jpeg"},
        {".js", "text/javascript"},
        {".json", "application/json"},
        {".jsonld", "application/ld+json"},
        {".mid", "audio/midi"},
        {".midi", "audio/x-midi"},
        {".mjs", "text/javascript"},
        {".mp3", "audio/mpeg"},
        {".mpeg", "video/mpeg"},
        {".mpkg", "application/vnd.apple.installer+xml"},
        {".odp", "application/vnd.oasis.opendocument.presentation"},
        {".ods", "application/vnd.oasis.opendocument.spreadsheet"},
        {".odt", "application/vnd.oasis.opendocument.text"},
        {".oga", "audio/ogg"},
        {".ogv", "video/ogg"},
        {".ogx", "application/ogg"},
        {".otf", "font/otf"},
        {".png", "image/png"},
        {".pdf", "application/pdf"},
        {".ppt", "application/vnd.ms-powerpoint"},
        {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
        {".rar", "application/x-rar-compressed"},
        {".rtf", "application/rtf"},
        {".sh", "application/x-sh"},
        {".svg", "image/svg+xml"},
        {".swf", "application/x-shockwave-flash"},
        {".tar", "application/x-tar"},
        {".tif", "image/tiff"},
        {".tiff", "image/tiff"},
        {".ttf", "font/ttf"},
        {".txt", "text/plain"},
        {".vsd", "application/vnd.visio"},
        {".wav", "audio/wav"},
        {".weba", "audio/webm"},
        {".webm", "video/webm"},
        {".webp", "image/webp"},
        {".woff", "font/woff"},
        {".woff2", "font/woff2"},
        {".xhtml", "application/xhtml+xml"},
        {".xls", "application/vnd.ms-excel"},
        {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
        {".xml", "application/xml"},
        {".xul", "application/vnd.mozilla.xul+xml"},
        {".zip", "application/zip"},
        {".3gp", "video/3gpp"},
        {".3g2", "video/3gpp2"},
        {".7z", "application/x-7z-compressed"}};

    // 当连接断开的时候继续发送数据会触发SIGPIPE异常,我们这里忽略这个信号
    class NetWork
    {
    public:
        NetWork()
        {
            // g_default_logger还没有初始化,所以会报段错误
            // LOG_INFO("SIGPIPE INIT");
            signal(SIGPIPE, SIG_IGN);
        }
    };
    static NetWork nw;

    class Util
    {
    public:
        // 字符串分割函数,将src字符串按照sep字符进行分割,得到的各个字串放到arry中,最终返回字串的数量
        static size_t Split(const std::string &src, const std::string &sep, std::vector<std::string> *array)
        {
            // abc,,,de,f,
            size_t offset = 0;
            // 有10个字符,offset是查找的起始位置,范围应该是0~9,offset==10就代表已经越界了
            while (offset < src.size())
            {
                // 在src字符串偏移量offset处,开始向后查找sep字符/字串,返回查找到的位置
                size_t pos = src.find(sep, offset);
                // 没有找到特定的字符将剩余的部分当作一个字串,放入array中
                if (pos == std::string::npos)
                {
                    if (pos == src.size())
                        break;
                    array->push_back(src.substr(offset));
                    return array->size();
                }
                // 连续分割字符则跳过字符
                if (offset == pos)
                {
                    offset = pos + sep.size();
                    continue;
                }
                array->push_back(src.substr(offset, pos - offset));
                offset = pos + sep.size();
            }

            return array->size();
        }

        // 读取文件的所有内容,将读取的内容放到一个Buffer中
        static bool ReadFile(const std::string &filename, std::string *buf)
        {
            std::ifstream ifs(filename, std::ios::binary);
            if (ifs.is_open() == false)
            {
                LOG_ERROR("{} open failed", filename.c_str());
                return false;
            }

            size_t fsize = 0;
            // 跳转读写位置到末尾
            ifs.seekg(0, ifs.end);
            // 获取当前读写位置相对于起始位置的偏移量,从末尾偏移刚好就是文件大小
            fsize = ifs.tellg();
            // 跳转到起始位置
            ifs.seekg(0, ifs.beg);
            // 开辟文件大小的空间
            buf->resize(fsize);

            ifs.read(&(*buf)[0], fsize);
            if (ifs.good() == false)
            {
                LOG_ERROR("{} read failed", filename.c_str());
                ifs.close();
                return false;
            }

            ifs.close();
            return true;
        }

        // 向文件写入数据
        static bool WriteFile(const std::string &filename, const std::string &buf)
        {
            std::ofstream ofs(filename, std::ios::binary | std::ios::trunc);
            if (ofs.is_open() == false)
            {
                LOG_ERROR("{} open failed", filename.c_str());
                return false;
            }

            ofs.write(buf.c_str(), buf.size());
            if (ofs.good() == false)
            {
                LOG_ERROR("{} write failed", filename.c_str());
                ofs.close();
                return false;
            }

            ofs.close();
            return true;
        }

        // URL编码,避免URL中资源路径与查询字符串中的特殊字符与HTTP请求中特殊字符产生歧义
        // 编码格式:将特殊字符的ascii值,转换为两个16进制字符,前缀%   C++ -> C%2B%2B
        // 不编码的特殊字符： RFC3986文档规定 . - _ ~ 字母,数字属于绝对不编码字符
        // RFC3986文档规定,编码格式 %HH
        // W3C标准中规定,查询字符串中的空格,需要编码为+, 解码则是+转空格
        static std::string UrlEncode(const std::string &url, bool convert_space_to_plus = false)
        {
            std::string res;
            for (auto ch : url)
            {
                // isalnum 是 C++ 中用于判断字符是否为字母或数字的函数
                if (ch == '.' || ch == '-' || ch == '_' || ch == '~' || isalnum(ch))
                {
                    res += ch;
                    continue;
                }
                if (ch == ' ' && convert_space_to_plus)
                {
                    res += '+';
                    continue;
                }

                // 剩下的字符都是需要编码成为 %HH 格式
                char tmp[4] = {0};
                snprintf(tmp, 4, "%%%02X", ch);
                res += tmp;
            }

            return res;
        }

        static char HEXTOI(const char ch)
        {
            if (ch >= '0' && ch <= '9')
            {
                return ch - '0';
            }
            else if (ch >= 'a' && ch <= 'z')
            {
                return ch - 'a' + 10;
            }
            else if (ch >= 'A' && ch <= 'Z')
            {
                return ch - 'A' + 10;
            }
            else
                return -1;
        }

        static std::string UrlDecode(const std::string &url, bool convert_plus_to_space)
        {
            // 遇到了%,则将紧随其后的2个字符,转换为数字,
            //  第一个数字左移4位,然后加上第二个数字  + -> 2b  %2b->2 << 4 + 11
            std::string res;
            for (int i = 0; i < url.size(); i++)
            {
                if (url[i] == '%' && (i + 2) < url.size())
                {
                    char val1 = HEXTOI(url[i + 1]);
                    char val2 = HEXTOI(url[i + 2]);
                    char val = val1 * 16 + val2;
                    i += 2;
                    res += val;
                    continue;
                }
                if (url[i] == '+' && convert_plus_to_space)
                {
                    res += ' ';
                    continue;
                }
                res += url[i];
            }

            return res;
        }

        // 响应状态码的描述信息获取
        static std::string StatuDesc(int statu)
        {
            auto it = _statu_msg.find(statu);
            if (it != _statu_msg.end())
            {
                return it->second;
            }
            return "UnKnown";
        }

        // 根据文件后缀名获取文件mime
        static std::string ExtMime(const std::string &filename)
        {
            // a.b.txt  先获取文件扩展名
            size_t pos = filename.rfind(".");
            if (pos == std::string::npos)
            {
                return "application/octet-stream";
            }

            // 根据扩展名，获取mime
            std::string ext = filename.substr(pos);
            auto it = _mime_msg.find(ext);
            if (it == _mime_msg.end())
            {
                return "application/octet-stream";
            }
            return it->second;
        }

        // 判断一个文件是否是一个目录
        static bool IsDirectory(const std::string &filename)
        {
            struct stat st;
            // stat 函数用于获取文件的状态信息，包括文件大小、权限、最后访问时间等
            // int stat(const char *path, struct stat *buf);
            int ret = stat(filename.c_str(), &st);
            if (ret < 0)
            {
                return false;
            }

            // S_ISDIR 是一个用于检查文件类型的宏，通常在 <sys/stat.h> 头文件中定义。
            // 这个宏用于确定传递给 stat 函数的文件是否为目录
            // #define S_ISDIR(mode)  (((mode) & S_IFMT) == S_IFDIR)

            return S_ISDIR(st.st_mode);
        }

        // 判断一个文件是否是一个普通文件
        static bool IsRegular(const std::string &filename)
        {
            struct stat st;
            int ret = stat(filename.c_str(), &st);
            if (ret < 0)
            {
                return false;
            }

            // #define S_ISREG(mode)  (((mode) & S_IFMT) == S_IFREG)
            // 这个宏用于确定传递给 stat 函数的文件是否为常规文件（regular file）
            return S_ISREG(st.st_mode);
        }

        // http请求的资源路径有效性判断
        //  /index.html  --- 前边的/叫做相对根目录  映射的是某个服务器上的子目录
        //  想表达的意思就是，客户端只能请求相对根目录中的资源，其他地方的资源都不予理会
        //  /../login, 这个路径中的..会让路径的查找跑到相对根目录之外，这是不合理的，不安全的
        static bool ValidPath(const std::string &path)
        {
            // 思想：按照/进行路径分割，根据有多少子目录，计算目录深度，有多少层，深度不能小于0
            std::vector<std::string> subdir;
            Split(path, "/", &subdir);

            int level = 0;
            for (auto &dir : subdir)
            {
                if (dir == "..")
                {
                    level--;
                    // 任意一层走出相对根目录,就认为有问题
                    if (level < 0)
                        return false;
                }
                else
                    level++;
            }

            return true;
        }
    };
}